<?php
/**
 * I-Cafe framework
 * 
 * @category CF
 * @package CF_Db
 * @link http://www.i-cafe.org
 * @license http://www.i-cafe.org/license
 * @version 04-04-2010
 * @author tcl_java@yahoo.com
 */

/**
 * A class perfomance for build and execute query UPDATE
 * <code>
 * 	$data = array("price" => 400);
 * 	$update = new CF_Db_Update();
 * 	$update->table("products")
 * 		->data($data)
 * 		->where("id=%u", 3);
 * 	echo $update; // output: "UPDATE `products` SET `price` = 400 WHERE `id` = 3"
 * </code>
 */
class CF_Db_Update extends CF_Component implements CF_Db_Query_Interface
{
	/**
	 * @var array
	 */
	protected $_table;
	
	/**
	 * @var array
	 */
	protected $_join;
	
	/**
	 * @var array
	 */
	protected $_columns;
	
	/**
	 * @var array
	 */
	protected $_values;
	
	/**
	 * @var array
	 */
	protected $_where;
	
	/**
	 * @var array
	 */
	protected $_order;
	
	/**
	 * @var string
	 */
	protected $_limit;
	
	/**
	 * 
	 */
	public function __construct()
	{
		# notifty before construct event
		$this->fireEvent('beforeconstruct', array($this));
		
		# register event
		$this->addEvents(
			'beforebuildquery',
			'buildquery',
			'beforeexecute',
			'execute'
		);
		
		$this->reset();
		
		# notify construct event
		$this->fireEvent('construct', array($this));
	}
	
	/**
	 * 
	 */
	public function reset()
	{
		$this->_table = array();
		$this->_join = array();
		$this->_columns = array();
		$this->_values = array();
		$this->_where = array('WHERE 1');
		$this->_order = array();
		$this->_limit = null;
	}
	
	/**
	 * 
	 * @param string|array|CF_Db_Expr $table
	 * @return CF_Db_Update this
	 */
	public function table($table, $data = null, $where = null)
	{
		if(is_string($table) || $table instanceof CF_Db_Expr)
			$table = array($table);
			
		if($data) $this->data($data);
		if($where) $this->where($where);
			
		$this->_table = $table;
		return $this;
	}
	
	/**
	 * 
	 * @param string $type
	 * @param string|array|CF_Db_Expr $table
	 * @param string $cond
	 * @return CF_Db_Select this
	 */
	protected function _join($type, $table, $cond)
	{
		$this->_join[] = array(
			'type' 		=> $type,
			'table' 	=> $table,
			'cond' 		=> $cond
		);
		
		return $this;
	}
	
	/**
	 * 
	 * @param string|array|CF_Db_Expr $table
	 * @param string $cond
	 * @return CF_Db_Select this
	 */
	public function join($table, $cond)
	{
		return $this->_join('', $table, $cond);
	}
	
	/**
	 * 
	 * @param string|array|CF_Db_Expr $table
	 * @param string $cond
	 * @return CF_Db_Select this
	 */
	public function joinInner($table, $cond)
	{
		return $this->_join('INNER', $table, $cond);
	}
	
	/**
	 * 
	 * @param string|array|CF_Db_Expr $table
	 * @param string $cond
	 * @return CF_Db_Select this
	 */
	public function joinCross($table, $cond)
	{
		return $this->_join('CROSS', $table, $cond);
	}
	
	/**
	 * 
	 * @param string|array|CF_Db_Expr $table
	 * @param string $cond
	 * @return CF_Db_Select this
	 */
	public function joinLeft($table, $cond)
	{
		return $this->_join('LEFT', $table, $cond);
	}
	
	/**
	 * 
	 * @param string|array|CF_Db_Expr $table
	 * @param string $cond
	 * @return CF_Db_Select this
	 */
	public function joinRight($table, $cond)
	{
		return $this->_join('RIGHT', $table, $cond);
	}
	
	/**
	 * 
	 * @param string|array|CF_Db_Expr $table
	 * @param string $cond
	 * @return CF_Db_Select this
	 */
	public function joinNatural($table, $cond)
	{
		return $this->_join('NATURAL', $table, $cond);
	}
	
	/**
	 * 
	 * @param string $type
	 * @param string|array|CF_Db_Expr $table
	 * @param string|array|CF_Db_Expr $using
	 * @return CF_Db_Select this
	 */
	protected function _joinUsing($type, $table, $using)
	{
		$this->_join[] = array(
			'type' 		=> $type,
			'table' 	=> $table,
			'columns'	=> $using
		);
		
		return $this;
	}
	
	/**
	 * 
	 * @param string|array|CF_Db_Exxpr $table
	 * @param string|array|CF_Db_Expr $using
	 * @return CF_Db_Select this
	 */
	public function joinUsing($table, $using)
	{
		return $this->_joinUsing('', $table, $using);
	}
	
	/**
	 * 
	 * @param string|array|CF_Db_Exxpr $table
	 * @param string|array|CF_Db_Expr $using
	 * @return CF_Db_Select this
	 */
	public function joinInnerUsing($table, $using)
	{
		return $this->_joinUsing('INNER', $table, $using);
	}
	
	/**
	 * 
	 * @param string|array|CF_Db_Exxpr $table
	 * @param string|array|CF_Db_Expr $using
	 * @return CF_Db_Select this
	 */
	public function joinCrossUsing($table, $using)
	{
		return $this->_joinUsing('CROSS', $table, $using);
	}
	
	/**
	 * 
	 * @param string|array|CF_Db_Exxpr $table
	 * @param string|array|CF_Db_Expr $using
	 * @return CF_Db_Select this
	 */
	public function joinLeftUsing($table, $using)
	{
		return $this->_joinUsing('LEFT', $table, $using);
	}
	
	/**
	 * 
	 * @param string|array|CF_Db_Exxpr $table
	 * @param string|array|CF_Db_Expr $using
	 * @return CF_Db_Select this
	 */
	public function joinRightUsing($table, $using)
	{
		return $this->_joinUsing('RIGHT', $table, $using);
	}
	
	/**
	 * 
	 * @param string|array|CF_Db_Exxpr $table
	 * @param string|array|CF_Db_Expr $using
	 * @return CF_Db_Select this
	 */
	public function joinNaturalUsing($table, $using)
	{
		return $this->_joinUsing('NATURAL', $table, $using);
	}
	
	/**
	 * 
	 * @param string|array|CF_Db_Expr $columns
	 * @return CF_Db_Update this
	 */
	public function columns($columns)
	{
		$this->_columns = $columns;
		return $this;
	}
	
	/**
	 * 
	 * @param string|array|CF_Db_Expr $values
	 * @return CF_Db_Update this
	 */
	public function values($values)
	{
		$this->_values = $values;
		return $this;
	}
	
	/**
	 * 
	 * @param array $data
	 * @return CF_Db_Update this
	 */
	public function data(array $data)
	{
		$this->_columns = array_keys($data);
		$this->_values = array_values($data);
		
		return $this;
	}
	
	/**
	 * 
	 * @param string|CF_Db_Expr $cond
	 * @param mixed $values [optional]
	 * @return CF_Db_Update this
	 */
	public function where($cond, $values = null)
	{
		$where = CF_Db_QueryPart::where($cond, $values);
		if($where) $this->_where[] = $where;
		
		return $this;
	}
	
	/**
	 * 
	 * @param string|CF_Db_Expr $cond
	 * @param mixed $values [optional]
	 * @return 
	 */
	public function orWhere($cond, $values = null)
	{
		$where = CF_Db_QueryPart::orWhere($cond, $values);
		if($where) $this->_where[] = $where;
		return $this;
	}
	
	/**
	 * 
	 * @param string|array $columns
	 * @param string $direction [optional]
	 * @return CF_Db_Update this
	 */
	public function order($columns, $direction = null)
	{
		$order = CF_Db_QueryPart::order($columns, $direction);
		if($order) $this->_order[] = $order;
		return $this;
	}
	
	/**
	 * 
	 * @param int $count
	 * @param int $offset [optional]
	 * @return CF_Db_Update this
	 */
	public function limit($count, $offset = null)
	{
		$this->_limit = CF_Db_QueryPart::limit($count, $offset);
		return $this;
	}
	
	/**
	 * Execute UPDATE query
	 * @return int
	 */
	public function execute()
	{
		$query = $this->__toString();
		
		# notify before execute event
		if(CF_Observable::VALUE_RETURN === $this->fireEvent('beforeexecute', array($this, &$query))) return;
		
		CF_Db::getInstance()->query($query);
		$res = CF_Db::getInstance()->getAfftectedRows();
		
		# notify execute event
		$this->fireEvent('execute', array($this, &$res, &$query));
		return $res;
	}
	
	/**
	 * Build query update
	 * @return string
	 */
	public function __toString()
	{
		# notify before build query event
		if(CF_Observable::VALUE_RETURN === $this->fireEvent('beforebuildquery', array($this))) return;
		
		$parts = array();
		$parts[] = 'UPDATE';
		
		# render table
		$alias = array_keys($this->_table);
		$names = array_values($this->_table);
		
		foreach($alias as &$a)
			$a = CF_Db::getInstance()->quoteIdentifier($a);
			
		foreach($names as &$n)
			$n = CF_Db::getInstance()->quoteIdentifier($n);
		
		$_table = $this->_table;
		$_table = array_combine($alias, $names);
		
		$tb = array();
		foreach($_table as $alias => $name){
			$tb[] = $name . (is_string($alias)? " AS $alias" : '');	
		}
		
		$parts[] = implode(',', $tb);
		
		# render join
		$_join = $this->_join;
		foreach($_join as $join){
			$j = $join['type']? $join['type'] . ' JOIN ' : 'JOIN ';
			
			if($join['table'] instanceof CF_Db_Expr){
				$j .= $join['table'];
			}elseif(is_string($join['table'])){
				$j .= CF_Db::getInstance()->quoteIdentifier($join['table']);
			}else{
				$j .= CF_Db::getInstance()->quoteIdentifier(current($join['table']));
				if(is_string($alias = key($join['table']))){
					$j .= ' AS ' . CF_Db::getInstance()->quoteIdentifier($alias);
				}
			}
			
			if($join['columns']){
				$j .= ' USING ';
				if($join['columns'] instanceof CF_Db_Expr){
					$cond = (string) $join['columns'];
				}else{
					if(is_string($join['columns'])){
						$join['columns'] = preg_replace('/\s+/', '', $join['columns']);
						$join['columns'] = explode(',', $join['columns']);
					}
					foreach($join['columns'] as &$col){
						$col = CF_Db::getInstance()->quoteIdentifier($col);
					}
					$cond = '(' . implode(',', $join['columns']) . ')';
				}
			}else{
				$j .= ' ON ';
				$cond = $join['cond'];
			}
			$j .= $cond;
			$parts[] = $j;
		}
				
		# render SET
		$_columns = $this->_columns;
		if(is_string($_columns)){
			$_columns = preg_replace('/\s+/', '', $_columns);
			$_columns = explode(',', $_columns);
		}
		
		foreach($_columns as &$col){
			$col = CF_Db::getInstance()->quoteIdentifier($col);
		}
		
		$_values = $this->_values;
		if(is_string($_values))
			$_values = array($_values);
		
		$_values = CF_Db::getInstance()->quote($_values);
		
		$data = array_combine($_columns, $_values);
		$set = array();
		foreach($data as $k => $v){
			$set[] = "$k=$v";
		}
		
		$parts[] = 'SET ' . implode(',', $set);
		
		# render where
		$parts[] = implode(' ', $this->_where);
		
		# render order
		if($this->_order){
			$parts[] = 'ORDER BY ' . implode(',', $this->_order);
		}
		
		# render limit
		if($this->_limit){
			$parts[] = $this->_limit;
		}
		
		$query = implode(' ', $parts);
		
		# notify build query event
		$this->fireEvent('buildquery', array($this, &$query));
		
		return $query;
	}
}
